﻿using System;
using System.Windows;
using System.Windows.Media.Animation;

namespace CodeAngel.UI.Extensions
{
    public static class AnimateExtensions
    {
        #region Animate

        /// <summary>Identifies the default duration to use for animations.</summary>
        private static Duration animationDuration = new Duration(TimeSpan.FromSeconds(.2));

        /// <summary>
        /// Animates the height of the framework element from its current value to the new value.
        /// </summary>
        /// <remarks>The duration will be based on the animationDuration setting defined in this extension class.</remarks>
        /// <param name="element">The framework element whose height will be animated.</param>
        /// <param name="newValue">The value to animate to.</param>
        public static void AnimateHeight(this FrameworkElement element, double newValue)
        {
            var storyboard = new Storyboard();
            storyboard.AddAnimation(element, FrameworkElement.HeightProperty, element.ActualHeight, newValue);
            storyboard.Begin();
        }

        /// <summary>
        /// Animates the height of the framework element from its current value to the new value.  When the 
        /// animation has completed, the onCompleted action will be invoked.
        /// </summary>
        /// <remarks>The duration will be based on the animationDuration setting defined in this extension class.</remarks>
        /// <param name="element">The framework element whose height will be animated.</param>
        /// <param name="newValue">The value to animate to.</param>
        /// <param name="onCompleted">The action to invoke when the animation has completed.</param>
        public static void AnimateHeight(this FrameworkElement element, double newValue, Action onCompleted)
        {
            var storyboard = new Storyboard();
            var animation = storyboard.AddAnimation(element, FrameworkElement.HeightProperty, element.ActualHeight, newValue);
            animation.Completed += delegate { onCompleted.Invoke(); };
            storyboard.Begin();
        }

        /// <summary>
        /// Animates the opacity of the framework element from its current value to the new value.
        /// </summary>
        /// <param name="element">The framework element whose opacity will be animated.</param>
        /// <param name="newValue">The value to animate to.</param>
        public static void AnimateOpacity(this FrameworkElement element, double newValue)
        {
            if (element.Visibility != Visibility.Visible) element.Visibility = Visibility.Visible;
            var storyboard = new Storyboard();
            storyboard.AddAnimation(element, FrameworkElement.OpacityProperty, element.Opacity, newValue);
            storyboard.Begin();
        }

        /// <summary>
        /// Animates the opacity of the framework element from its current value to the new value.  When the 
        /// animation has completed, the visibility of that element will be set to the visibilityWhenDone parameter.
        /// </summary>
        /// <param name="element">The framework element whose opacity will be animated.</param>
        /// <param name="newValue">The value to animate to.</param>
        /// <param name="visibilityWhenDone">The visibility to set the element when the animation has completed.</param>
        public static void AnimateOpacity(this FrameworkElement element, double newValue, Visibility visibilityWhenDone)
        {
            if (element.Visibility != Visibility.Visible) element.Visibility = Visibility.Visible;
            var storyboard = new Storyboard();
            storyboard.AddAnimation(element, FrameworkElement.OpacityProperty, element.Opacity, newValue).Completed += delegate
            {
                element.Visibility = visibilityWhenDone;
            };
            storyboard.Begin();
        }

        /// <summary>
        /// Animates the opacity of the framework element from its current value to the new value.  When the 
        /// animation has completed, the onCompleted action will be invoked.
        /// </summary>
        /// <remarks>The duration will be based on the animationDuration setting defined in this extension class.</remarks>
        /// <param name="element">The framework element whose opacity will be animated.</param>
        /// <param name="newValue">The value to animate to.</param>
        /// <param name="onCompleted">The action to invoke when the animation has completed.</param>
        public static void AnimateOpacity(this FrameworkElement element, double newValue, Action onCompleted)
        {
            if (element.Visibility != Visibility.Visible) element.Visibility = Visibility.Visible;
            var storyboard = new Storyboard();
            storyboard.AddAnimation(element, FrameworkElement.OpacityProperty, element.Opacity, newValue).Completed += delegate
            {
                onCompleted.Invoke();
            };
            storyboard.Begin();
        }

        /// <summary>
        /// Animates a property of an element.
        /// </summary>
        /// <param name="element">The element.</param>
        /// <param name="property">The property of the element to animate.</param>
        /// <param name="fromValue">The value to animate from.</param>
        /// <param name="toValue">The value to animate to.</param>
        public static void Animate(this UIElement element, DependencyProperty property, double fromValue, double toValue)
        {
            var storyboard = new Storyboard();
            storyboard.AddAnimation(element, property, fromValue, toValue);
            storyboard.Begin();
        }

        /// <summary>
        /// Adds a new double-animation to the current storyboard.
        /// </summary>
        /// <param name="storyboard">The storyboard to add the animation to.</param>
        /// <param name="target">The target UI element.</param>
        /// <param name="targetProperty">The property of the target UI element that will be animated when the storyboard is run.</param>
        /// <param name="fromValue">The value the animation will start from.</param>
        /// <param name="toValue">The value the animation will go to.</param>
        /// <returns>The newly created and added double-animation.</returns>
        public static DoubleAnimation AddAnimation(this Storyboard storyboard, UIElement target, DependencyProperty targetProperty, double fromValue, double toValue)
        {
            return storyboard.AddAnimation(target, targetProperty, fromValue, toValue, animationDuration);
        }

        /// <summary>
        /// Adds a new double-animation to the current storyboard.
        /// </summary>
        /// <param name="storyboard">The storyboard to add the animation to.</param>
        /// <param name="target">The target UI element.</param>
        /// <param name="targetProperty">The property of the target UI element that will be animated when the storyboard is run.</param>
        /// <param name="fromValue">The value the animation will start from.</param>
        /// <param name="toValue">The value the animation will go to.</param>
        /// <param name="duration">Identifies how long the animation should last.</param>
        /// <returns>The newly created and added double-animation.</returns>
        public static DoubleAnimation AddAnimation(this Storyboard storyboard, UIElement target, DependencyProperty targetProperty, double fromValue, double toValue, Duration duration)
        {
            return storyboard.AddAnimation(target, targetProperty, fromValue, toValue, duration, TimeSpan.FromSeconds(0));
        }

        /// <summary>
        /// Adds a new double-animation to the current storyboard.
        /// </summary>
        /// <param name="storyboard">The storyboard to add the animation to.</param>
        /// <param name="target">The target UI element.</param>
        /// <param name="targetProperty">The property of the target UI element that will be animated when the storyboard is run.</param>
        /// <param name="fromValue">The value the animation will start from.</param>
        /// <param name="toValue">The value the animation will go to.</param>
        /// <param name="duration">Identifies how long the animation should last.</param>
        /// <param name="beginTime">Identifies at what point in the storyboard this animation should begin.</param>
        /// <returns>The newly created and added double-animation.</returns>
        public static DoubleAnimation AddAnimation(this Storyboard storyboard, UIElement target, DependencyProperty targetProperty, double fromValue, double toValue, Duration duration, Duration beginTime)
        {
            return storyboard.AddAnimation(target, targetProperty, fromValue, toValue, duration, beginTime.TimeSpan);
        }

        /// <summary>
        /// Adds a new double-animation to the current storyboard.
        /// </summary>
        /// <param name="storyboard">The storyboard to add the animation to.</param>
        /// <param name="target">The target UI element.</param>
        /// <param name="targetProperty">The property of the target UI element that will be animated when the storyboard is run.</param>
        /// <param name="fromValue">The value the animation will start from.</param>
        /// <param name="toValue">The value the animation will go to.</param>
        /// <param name="duration">Identifies how long the animation should last.</param>
        /// <param name="beginTime">Identifies at what point in the storyboard this animation should begin.</param>
        /// <returns>The newly created and added double-animation.</returns>
        public static DoubleAnimation AddAnimation(this Storyboard storyboard, UIElement target, DependencyProperty targetProperty, double fromValue, double toValue, Duration duration, TimeSpan beginTime)
        {
            var animation = new DoubleAnimation(fromValue, toValue, duration);
            animation.AutoReverse = false;
            animation.BeginTime = beginTime;
            Storyboard.SetTarget(animation, target);
            Storyboard.SetTargetProperty(animation, new PropertyPath(targetProperty));
            storyboard.Children.Add(animation);
            return animation;
        }

        #endregion

    }
}
